%%
%% Simple Echo example
%% @author Dave Bryson [http://weblog.miceda.org]
%%
%% basic 封装
-module(basic_websocket).

-include("common.hrl").

-export([start/0, loop/2, broadcast_server/1]).

start() ->
	GamePort = data_setting:get(game_port),
	MainPID = spawn(
		fun() ->
			Broadcaster = spawn_link(?MODULE, broadcast_server, [dict:new()]),
			mochiweb_http:start_link([
				{name, ?MODULE},
				{loop, {?MODULE, loop, [Broadcaster]}},
				{port, GamePort},
				{ip, "0.0.0.0"}
			]),
			receive
				stop -> ok
			end
		end
	),
	{ok, MainPID}.

loop(Req, Broadcaster) ->
	H = mochiweb_request:get_header_value("Upgrade", Req),
	loop(Req,
		Broadcaster,
		H =/= undefined andalso string:to_lower(H) =:= "websocket").

loop(Req, _Broadcaster, false) ->
	?ERR("not websocket Req = ~p", [Req]);
loop(Req, Broadcaster, true) ->
	{InnerLoopFun, SendToSocketFun} = mochiweb_websocket:upgrade_connection(
		Req, fun ws_loop/3),
	Socket = Req:get(socket),
	Broadcaster ! {register, self(), SendToSocketFun, Socket},
	timer_wheel:init(),
	Now = util:now(),
	timer_wheel:plan(Now + 10, fun heartBeat/1),
	InnerLoopFun(Broadcaster).

ws_loop(Payload, Broadcaster, _SendToSocketFun) ->
	GWPID = self(),
	route_msg(GWPID, Payload),
	%%更新心跳时间
	catch(ets:update_element(?ETS_ROLE_GATEWAY,GWPID,{#role_gateway.heartBeatTime,util:now()})),

	Broadcaster.

%% This server keeps track of connected pids
broadcast_server(Pids) ->
	Pids1 = receive
		        {register, Pid, SendToSocketFun, Socket} ->
			        broadcast_register(Pid, SendToSocketFun, Pids, Socket);
		        {broadcast, Pid, Message} ->
			        broadcast_sendall(Pid, Message, Pids);
		        {'DOWN', MRef, process, Pid, _Reason} ->
			        broadcast_down(Pid, MRef, Pids);
		        Msg ->
			        ?ERR("Unknown message: ~p", [Msg]),
			        Pids
	        end,
	erlang:hibernate(?MODULE, broadcast_server, [Pids1]).

broadcast_register(Pid, SendToSocketFun, Pids, Socket) ->
	MRef = erlang:monitor(process, Pid),
	%%启动玩家进程
	{ok, ChildPID} = supervisor:start_child(role_sup, [{SendToSocketFun, Socket}]),
	ets:insert(?ETS_ROLE_GATEWAY, #role_gateway{roleGWPID = Pid, rolePID = ChildPID, sendToSocketFun = SendToSocketFun,heartBeatTime = util:now()}),
	NewPids = dict:store(Pid, {SendToSocketFun, MRef}, Pids),
	NewPids.

broadcast_down(Pid, MRef, Pids) ->
	Pids1 = case dict:find(Pid, Pids) of
		        {ok, {_, MRef}} ->
			        dict:erase(Pid, Pids);
		        _ ->
			        Pids
	        end,
	%%给玩家进程发消息，网关DOWN
	RolePID = util:getEtsElement(?ETS_ROLE_GATEWAY,Pid,#role_gateway.rolePID,?UNDEFINED),
	?INFO("gateway_down RolePID = ~p",[RolePID]),
	?CATCH(gen_server:cast(RolePID,{gateway_down})),
	ets:delete(?ETS_ROLE_GATEWAY,Pid),
	Pids1.

broadcast_sendall(_Pid, Msg, Pids) ->
	NewMsg = proto:encode(Msg),
	?INFO("send to client = ~p~n", [NewMsg]),
	NewPids = dict:fold(
		fun(PidKey, {SendToSocketFun, MRef}, Acc) ->
			try
				begin
					SendToSocketFun(NewMsg),
					dict:store(PidKey, {SendToSocketFun, MRef}, Acc)
				end
			catch
				_:_ ->
					Acc
			end
		end,
		dict:new(),
		Pids),
	NewPids.

route_msg(GWPID, Json) ->
	Record = proto:decode(Json),
	?DEBUG("from client Record = ~p~n", [Record]),
	case proto_route:route(element(1, Record)) of
		{role, HandleModule} ->
			RolePID = util:getEtsElement(?ETS_ROLE_GATEWAY, GWPID, #role_gateway.rolePID, ?UNDEFINED),
			?CATCH(erlang:send(RolePID, {client_msg, HandleModule, Record}));
		{Server, _HandleModule} ->
			RoleID = util:getEtsElement(?ETS_ROLE_GATEWAY, GWPID, #role_gateway.roleID, ?UNDEFINED),
			catch erlang:send(Server, {client_msg, RoleID, Record})
	end.

heartBeat(Now) ->
	LastTime = util:getEtsElement(?ETS_ROLE_GATEWAY,self(),#role_gateway.heartBeatTime,0),
	case Now - LastTime > ?HEART_BEAT_TIME_OUT of
		?TRUE -> self() ! {tcp_closed, heartBeatTimeOut};
		_ -> timer_wheel:plan(Now + 10, fun heartBeat/1)
	end.